package nachos.network;

import nachos.machine.*;

import java.util.*;

/**
 * A mail message. Includes a packet header, a mail header, and the actual
 * payload.
 *
 * @see	nachos.machine.Packet
 */
public class MailMessage
{

    /**
     * Allocate a new mail message to be sent, using the specified parameters.
     *
     * @param	dstLink		the destination link address.
     * @param	dstPort		the destination port.
     * @param	srcLink		the source link address.
     * @param	srcPort		the source port.
     * @param	contents	the contents of the packet.
     */
    public MailMessage(int dstLink, int dstPort, int srcLink, int srcPort, int seq, byte[] contents) throws MalformedPacketException
    {
        // make sure the paramters are valid
        if (dstPort < 0 || dstPort >= portLimit ||
                srcPort < 0 || srcPort >= portLimit ||
                contents.length > maxContentsLength)
        {
            throw new MalformedPacketException();
        }

        this.dstPort = (byte) dstPort;
        this.srcPort = (byte) srcPort;
        this.dstLink = dstLink;
        this.srcLink = srcLink;
        seqNum = seq;

        this.contents = contents;
    }

    /**
     * Allocate a new mail message using the specified packet from the network.
     *
     * @param	packet	the packet containg the mail message.
     */
    public MailMessage(Packet packet) throws MalformedPacketException
    {

        // make sure we have a valid header
        if (packet.contents.length < headerLength ||
                packet.contents[0] < 0 || packet.contents[0] >= portLimit ||
                packet.contents[1] < 0 || packet.contents[1] >= portLimit)
        {
            throw new MalformedPacketException();
        }

        dstPort = packet.contents[0];
        srcPort = packet.contents[1];
        if(packet.contents[2] != 0)
            throw new MalformedPacketException();
        flags = packet.contents[3];

        seqNum = Lib.bytesToInt(packet.contents, 4);

        srcLink = packet.srcLink;
        dstLink = packet.dstLink;

        contents = new byte[packet.contents.length - headerLength];
        System.arraycopy(packet.contents, headerLength, contents, 0,
                contents.length);
    }

    public void setFin()
    {
        flags = setBit(flags, FIN);
    }

    public boolean finSet()
    {
        return isSet(flags, FIN);
    }

    public void setSTP()
    {
        flags = setBit(flags, STP);
    }

    public boolean stpSet()
    {
        return isSet(flags, 4);
    }

    public void setAck()
    {
        flags = setBit(flags, ACK);
    }

    public boolean ackSet()
    {
        return isSet(flags, ACK);
    }

    public void setSyn()
    {
        flags = setBit(flags, SYN);
    }

    public boolean synSet()
    {
        return isSet(flags, SYN);
    }

    protected byte bool2int(boolean b)
    {
        if (b)
        {
            return 1;
        }
        else
        {
            return 0;
        }
    }

    protected boolean isSet(int f, int bit)
    {
        return (f & bit) == bit;
    }

    protected int setBit(int f, int bit)
    {
        return f |= bit;
    }

    public Packet getPacket()
    {
        byte[] packetContents = new byte[headerLength + contents.length];

        packetContents[0] = (byte) dstPort;
        packetContents[1] = (byte) srcPort;
        packetContents[2] = (byte) 0;
        packetContents[3] = (byte) flags;

        Lib.bytesFromInt(packetContents, 4, seqNum);

        System.arraycopy(contents, 0, packetContents, headerLength,
                contents.length);
        Packet p = null;
        try
        {
            p = new Packet(dstLink, srcLink, packetContents);
        }
        catch (MalformedPacketException ex)
        {

        }

        return p;
    }

    public static void selfTest()
    {
        try
        {
            MailMessage m = new MailMessage(0, 0, 0, 0, 0, new byte[MailMessage.maxContentsLength]);
            m.setAck();
            Lib.assertTrue(m.ackSet());

            m.setSyn();
            Lib.assertTrue(m.ackSet() && m.synSet());
        }
        catch (MalformedPacketException ex)
        {
            Lib.assertNotReached();
        }
    }

    /**
     * Return a string representation of the message headers.
     */
    public String toString()
    {
        return "from (" + srcLink + ":" + srcPort +
                ") to (" + dstLink + ":" + dstPort +
                "), " + contents.length + " bytes seqNum: " + seqNum;
    }

    /** The port used by this message on the destination machine. */
    public int dstPort;
    /** The port used by this message on the source machine. */
    public int srcPort;
    /** The contents of this message, excluding the mail message header. */
    public byte[] contents;
    public int dstLink;
    public int srcLink;
    public int flags;
    public int seqNum;
    protected final int FIN = 8;
    protected final int STP = 4;
    protected final int ACK = 2;
    protected final int SYN = 1;
    /**
     * The number of bytes in a mail header. The header is formatted as
     * follows:
     *
     * <table>
     * <tr><td>offset</td><td>size</td><td>value</td></tr>
     * <tr><td>0</td><td>1</td><td>destination port</td></tr>
     * <tr><td>1</td><td>1</td><td>source port</td></tr>
     * </table>
     */
    public static final int headerLength = 8;
    /** Maximum payload (real data) that can be included in a single mesage. */
    public static final int maxContentsLength =
            Packet.maxContentsLength - headerLength;
    /**
     * The upper limit on mail ports. All ports fall between <tt>0</tt> and
     * <tt>portLimit - 1</tt>.
     */
    public static final int portLimit = 128;
}
